package mirrormonkey.framework.test.listeners;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;

import java.util.Deque;
import java.util.LinkedList;

import mirrormonkey.core.EntityLifecycleListener;
import mirrormonkey.framework.ConnectionInfo;
import mirrormonkey.framework.entity.StaticEntityData;
import mirrormonkey.framework.entity.SyncEntity;

public class MockEntityLifecycleListener implements EntityLifecycleListener {

	public final Deque<EntityLifecycleEvent> recordedEvents;

	public int sortingOrder;

	public MockEntityLifecycleListener() {
		this(FRAMEWORK_MAX_SORTING_ORDER);
	}

	public MockEntityLifecycleListener(int sortingOrder) {
		recordedEvents = new LinkedList<EntityLifecycleEvent>();
		this.sortingOrder = sortingOrder;
	}

	@Override
	public void entityInitialized(SyncEntity entity,
			ConnectionInfo connectionInfo) {
		recordedEvents.add(new EntityInitializedEvent(entity, connectionInfo));
	}

	@Override
	public void entityReplaced(SyncEntity replacedEntity,
			StaticEntityData replacedStaticData, SyncEntity replacingEntity,
			ConnectionInfo connectionInfo) {
		recordedEvents.add(new EntityReplacedEvent(replacedEntity,
				replacedStaticData, replacingEntity, connectionInfo));
	}

	@Override
	public void entityReplacing(SyncEntity replacedEntity,
			StaticEntityData replacedStaticData, SyncEntity replacingEntity,
			ConnectionInfo connectionInfo) {
		recordedEvents.add(new EntityReplacingEvent(replacedEntity,
				replacedStaticData, replacingEntity, connectionInfo));
	}

	@Override
	public void entityRemoved(SyncEntity entity, ConnectionInfo connectionInfo) {
		recordedEvents.add(new EntityRemovedEvent(entity, connectionInfo));
	}

	public void assertEmpty() {
		assertTrue(recordedEvents.isEmpty());
	}

	public <T extends EntityLifecycleEvent> T pollFirst(Class<T> expectedClass) {
		assertFalse(recordedEvents.isEmpty());
		EntityLifecycleEvent e = recordedEvents.poll();
		assertEquals(expectedClass, e.getClass());
		return (T) e;
	}

	private <T extends SyncEntity> T assertEntityClass(boolean exact,
			SyncEntity e, Class<T> expectedClass) {
		if (exact) {
			assertEquals(expectedClass, e.getClass());
		} else {
			assertTrue(expectedClass.isAssignableFrom(e.getClass()));
		}
		return (T) e;
	}

	public SyncEntity assertInit(ConnectionInfo connectionInfo) {
		return assertInit(false, SyncEntity.class, connectionInfo);
	}

	public <T extends SyncEntity> T assertInit(boolean exact,
			Class<T> expectedClass, ConnectionInfo connectionInfo) {
		return assertInit(true, exact, expectedClass, connectionInfo);
	}

	public <T extends SyncEntity> T assertInit(boolean assertOnlyEvent,
			boolean exact, Class<T> expectedClass, ConnectionInfo connectionInfo) {
		EntityInitializedEvent e = pollFirst(EntityInitializedEvent.class);
		if (assertOnlyEvent) {
			assertEmpty();
		}
		assertSame(connectionInfo, e.connectionInfo);
		return assertEntityClass(exact, e.entity, expectedClass);
	}

	public SyncEntity assertReplace(SyncEntity replacedEntity,
			StaticEntityData replacedStaticData, ConnectionInfo connectionInfo) {
		return assertReplace(false, replacedEntity, replacedStaticData,
				SyncEntity.class, connectionInfo);
	}

	public <T extends SyncEntity> T assertReplace(boolean exact,
			SyncEntity oldEntity, StaticEntityData oldStaticData,
			Class<T> replacingEntityClass, ConnectionInfo connectionInfo) {
		return assertReplace(true, exact, oldEntity, oldStaticData,
				replacingEntityClass, connectionInfo);
	}

	public <T extends SyncEntity> T assertReplace(boolean assertOnlyEvent,
			boolean exact, SyncEntity oldEntity,
			StaticEntityData oldStaticData, Class<T> replacingEntityClass,
			ConnectionInfo connectionInfo) {
		EntityReplacedEvent replaced = pollFirst(EntityReplacedEvent.class);
		assertSame(connectionInfo, replaced.connectionInfo);
		assertSame(oldEntity, replaced.replacedEntity);
		assertSame(oldStaticData, replaced.replacedStaticData);
		EntityReplacingEvent replacing = pollFirst(EntityReplacingEvent.class);
		assertSame(replaced.replacedEntity, replacing.replacedEntity);
		assertSame(replaced.replacedStaticData, replacing.replacedStaticData);
		assertSame(replaced.replacingEntity, replacing.replacingEntity);
		if (assertOnlyEvent) {
			assertEmpty();
		}
		return assertEntityClass(exact, replaced.replacingEntity,
				replacingEntityClass);
	}

	public void assertRemoved(SyncEntity entity, ConnectionInfo connectionInfo) {
		assertRemoved(true, entity, connectionInfo);
	}

	public void assertRemoved(boolean assertOnlyEvent, SyncEntity entity,
			ConnectionInfo connectionInfo) {
		EntityRemovedEvent e = pollFirst(EntityRemovedEvent.class);
		assertSame(connectionInfo, e.connectionInfo);
		if (assertOnlyEvent) {
			assertEmpty();
		}
		assertNotNull(entity);
		assertNotNull(e.entity);
		assertSame(entity, e.entity);
	}

	public void clearRecordedEvents() {
		recordedEvents.clear();
	}

	public static interface EntityLifecycleEvent {
	}

	public static class EntityInitializedEvent implements EntityLifecycleEvent {

		public final SyncEntity entity;

		public final ConnectionInfo connectionInfo;

		public EntityInitializedEvent(SyncEntity entity,
				ConnectionInfo connectionInfo) {
			this.entity = entity;
			this.connectionInfo = connectionInfo;
		}

	}

	public static class EntityReplacedEvent implements EntityLifecycleEvent {

		public final SyncEntity replacedEntity;

		public final StaticEntityData replacedStaticData;

		public final SyncEntity replacingEntity;

		public final ConnectionInfo connectionInfo;

		public EntityReplacedEvent(SyncEntity replacedEntity,
				StaticEntityData replacedStaticData,
				SyncEntity replacingEntity, ConnectionInfo connectionInfo) {
			this.replacedEntity = replacedEntity;
			this.replacedStaticData = replacedStaticData;
			this.replacingEntity = replacingEntity;
			this.connectionInfo = connectionInfo;
		}

	}

	public static class EntityReplacingEvent implements EntityLifecycleEvent {

		public final SyncEntity replacedEntity;

		public final StaticEntityData replacedStaticData;

		public final SyncEntity replacingEntity;

		public final ConnectionInfo connectionInfo;

		public EntityReplacingEvent(SyncEntity replacedEntity,
				StaticEntityData replacedStaticData,
				SyncEntity replacingEntity, ConnectionInfo connectionInfo) {
			this.replacedEntity = replacedEntity;
			this.replacedStaticData = replacedStaticData;
			this.replacingEntity = replacingEntity;
			this.connectionInfo = connectionInfo;
		}

	}

	public static class EntityRemovedEvent implements EntityLifecycleEvent {

		public final SyncEntity entity;

		public final ConnectionInfo connectionInfo;

		public EntityRemovedEvent(SyncEntity entity,
				ConnectionInfo connectionInfo) {
			this.entity = entity;
			this.connectionInfo = connectionInfo;
		}

	}

	@Override
	public int getSortingOrder() {
		return sortingOrder;
	}
}
